package cn.lingyangwl.agile.infra.dict.service.impl;

import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.hutool.core.io.IoUtil;
import cn.lingyangwl.agile.file.excel.*;
import cn.lingyangwl.agile.infra.dict.assembly.*;
import cn.lingyangwl.agile.infra.dict.manager.*;
import cn.lingyangwl.agile.infra.dict.model.*;
import cn.lingyangwl.agile.infra.dict.service.*;
import cn.lingyangwl.agile.infra.group.service.*;
import cn.lingyangwl.agile.infra.module.dict.model.entity.*;
import cn.lingyangwl.agile.infra.module.dict.model.rqrs.*;
import cn.lingyangwl.agile.infra.module.group.model.entity.DataGroup;
import cn.lingyangwl.agile.infra.module.group.model.rqrs.*;
import cn.lingyangwl.agile.model.*;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.constants.CommonCons.*;
import cn.lingyangwl.agile.model.enums.*;
import cn.lingyangwl.agile.system.constants.*;
import cn.lingyangwl.framework.mybatis.mate.annotations.*;
import cn.lingyangwl.framework.mybatis.mate.enums.*;
import cn.lingyangwl.framework.mybatis.plus.utils.*;
import cn.lingyangwl.framework.tool.core.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author shenguangyang
 */
@Slf4j
@Service
public class DictServiceImpl implements DictService {
    @Resource
    private DictManager dictManager;
    @Resource
    private DictItemManager dictItemManager;
    @Resource
    private DictAssembly dictAssembly;
    @Resource
    private DataGroupService dataGroupService;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private DictMqService dictMqService;

    public LambdaQueryWrapper<Dict> getQueryWrapper(DictQueryReq query) {
        Assert.notNull(query, "非法数据");
        LocalDateTime beginTime = query.getBeginLocalDateTime();
        LocalDateTime endTime = query.getEndLocalDateTime();
        return Dict.lqw()
                .like(StringUtils.isNotEmpty(query.getDictName()), Dict::getDictName, query.getDictName())
                .eq(StringUtils.isNotEmpty(query.getStatus()), Dict::getStatus, query.getStatus())
                .eq(StringUtils.isNotEmpty(query.getGroupId()), Dict::getGroupId, query.getGroupId())
                .like(StringUtils.isNotEmpty(query.getDictCode()), Dict::getDictCode, query.getDictCode())
                .ge(!Objects.isNull(beginTime), BasePlusPO::getCreatedAt, beginTime)
                .le(!Objects.isNull(endTime), BasePlusPO::getCreatedAt, endTime).orderByDesc(BasePlusPO::getCreatedAt);
    }

    @Override
    public List<Dict> list(DictQueryReq query) {
        return dictManager.list(getQueryWrapper(query));
    }

    @Override
    public IPage<DictResp> pageDict(DictQueryReq req) {
        IPage<Dict> respList = dictManager.page(PageUtil.to(req), getQueryWrapper(req));
        List<Long> groupIds = respList.getRecords().stream().map(Dict::getGroupId).distinct().collect(Collectors.toList());
        groupIds.add(-1L);
        Map<Long, String> groupMap = dataGroupService.listByIds(groupIds).stream()
                .collect(Collectors.toMap(DataGroup::getId, DataGroup::getName));
        return PageUtil.to(respList, (e) -> {
            DictResp resp = dictAssembly.toDictResp(e);
            resp.setGroupName(groupMap.get(resp.getGroupId()));
            return resp;
        });
    }

    @Override
    public List<Dict> listAll() {
        return dictManager.list(new QueryWrapper<>());
    }

    @Override
    public Map<String, List<DictItem>> listAllDictItem() {
        Map<String, List<DictItem>> data = new HashMap<>();
        List<Dict> dictList = dictManager.list(Dict.lqw());
        Map<String, List<DictItem>> dictItemMap = dictItemManager.list(DictItem.lqw())
                .stream().collect(Collectors.groupingBy(DictItem::getDictCode));
        for (Dict dict : dictList) {
            List<DictItem> dictItemList = dictItemMap.getOrDefault(dict.getDictCode(), Collections.emptyList())
                .stream().sorted(Comparator.comparing(DictItem::getSortNo)).collect(Collectors.toList());

            data.put(dict.getDictCode(), dictItemList);
        }
        return data;
    }

    @Override
    public Dict getById(Long dictId) {
        return dictManager.getOne(Dict.lqw().eq(Dict::getId, dictId));
    }

    @Override
    public Dict getByCode(String dictCode) {
        LambdaQueryWrapper<Dict> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Dict::getDictCode, dictCode);
        return dictManager.getOne(lqw);
    }

    @Override
    @Transactional
    public void deleteBatchByIds(Long[] dictIds) {
        List<Long> collect = Arrays.stream(dictIds).collect(Collectors.toList());
        dictManager.removeByIds(collect);
        dictItemManager.deleteByDictIds(collect);
    }

    @Override
    @Transactional
    public void deleteById(Long dictId) {
        dictManager.removeById(dictId);
        dictItemManager.deleteByDictId(dictId);
    }

    @Override
//    @CacheEvict(value = SysCacheCons.SYS_CONFIG, allEntries = true)
    public void resetDictCache() {

    }

    @FieldUniqueCheck(type = OperationTypeEnum.SAVE)
    @Override
    public boolean save(Dict dict) {
        initDefaultGroup(dict);
        dict.setStatus(StringUtils.firstNonEmpty(dict.getStatus(), UseStats.NORMAL));
        return dictManager.save(dict);
    }

    @FieldUniqueCheck(type = OperationTypeEnum.UPDATE)
    @Override
    public boolean updateById(Dict dict) {
        initDefaultGroup(dict);
        return dictManager.updateById(dict);
    }

    public void initDefaultGroup(Dict dict) {
        if (Objects.isNull(dict.getGroupId())) {
            DataGroupQuery listReq = new DataGroupQuery().setBizType(DataGroupBizTypeEnum.DICT.getType())
                    .setCode(CommonCons.DataGroup.DEFAULT_CODE);
            DataGroupDto defaultGroup = dataGroupService.listGroup(listReq).stream().findFirst().orElseThrow(() -> new BizException("默认组不存在"));
            dict.setGroupId(defaultGroup.getId());
        }
    }

    /**
     * 不需要开事务, 导入失败, 重新导入即可, 会自动校验重复数据
     */
    @Override
    public String importExcel(InputStream is) throws Exception {
        DataGroupQuery listReq = new DataGroupQuery()
            .setBizType(DataGroupBizTypeEnum.DICT.getType());
        List<DataGroupDto> groupList = dataGroupService.listGroup(listReq);
        Map<String, Long> groupMap = groupList.stream().collect(Collectors.toMap(DataGroupBase::getName, DataGroupDto::getId, (k1, k2) -> k1));
        DataGroupDto defaultGroup = groupList.stream().filter(e -> CommonCons.DataGroup.DEFAULT_CODE.equals(e.getCode()))
            .findFirst().orElseThrow(() -> new BizException("默认数据组不存在"));

        byte[] bytes = IoUtil.readBytes(is);
        try (InputStream sheet0 = new ByteArrayInputStream(bytes);
             InputStream sheet1 = new ByteArrayInputStream(bytes)) {
            ImportParams params = new ImportParams();
            params.setHeadRows(1);
            params.setTitleRows(1);
            params.setStartSheetIndex(0);
            List<DictExcel> importDictList = CusExcelImportUtil.getList(params, sheet0, DictExcel.class);

            params.setStartSheetIndex(1);
            List<DictItemExcel> dictItemList = CusExcelImportUtil.getList(params, sheet1, DictItemExcel.class);

            log.info("import dict count is [{}], dict item count is [{}]", importDictList.size(), dictItemList.size());

            // 获取所有的字典
            Set<String> dictTypes = importDictList.stream().map(DictExcel::getDictCode).collect(Collectors.toSet());
            dictTypes.addAll(dictItemList.stream().map(DictItemExcel::getDictCode).collect(Collectors.toSet()));

            Map<String, Dict> typeRecordMap = dictManager.list(Dict.lqw().in(Dict::getDictCode, dictTypes))
                .stream().collect(Collectors.toMap(Dict::getDictCode, Function.identity()));
            Map<String, DictItem> dictRecordMap = dictItemManager.list(DictItem.lqw().in(DictItem::getDictCode, dictTypes))
                .stream().collect(Collectors.toMap(e -> e.getDictCode() + e.getItemValue(), Function.identity()));

            // 字典sheet中定义的字典信息
            Map<String, Dict> dictMap = importDictList.stream()
                .map(e -> {
                    Long groupId = groupMap.get(e.getGroupName());
                    if (Objects.isNull(groupId) && StringUtils.isNotEmpty(e.getGroupName())) {
                        DataGroupSaveReq saveReq = new DataGroupSaveReq();
                        saveReq.setBizType(DataGroupBizTypeEnum.DICT.getType());
                        saveReq.setName(e.getGroupName());
                        groupId = dataGroupService.saveGroup(saveReq);
                        groupMap.put(e.getGroupName(), groupId);
                    }
                    Dict dict = dictAssembly.toDict(e);
                    dict.setId(typeRecordMap.getOrDefault(e.getDictCode(), new Dict()).getId());
                    dict.setGroupId(Objects.isNull(groupId) ? defaultGroup.getId() : groupId);
                    return dict;
                }).collect(Collectors.toMap(Dict::getDictCode, Function.identity()));

            // 值域sheet中定义的类型
            Map<String, Dict> dict2Map = dictItemList.stream().filter(e -> !dictMap.containsKey(e.getDictCode()))
                .map(e -> {
                    boolean exitType = typeRecordMap.containsKey(e.getDictCode());
                    Dict type = new Dict();
                    type.setId(typeRecordMap.getOrDefault(e.getDictCode(), new Dict()).getId());
                    type.setDictName(e.getDictName());
                    type.setDictCode(e.getDictCode());
                    type.setGroupId( exitType ? null : defaultGroup.getId());
                    type.setStatus(exitType ? null : CommonCons.UseStats.NORMAL);
                    return type;
                }).collect(Collectors.toMap(Dict::getDictCode, Function.identity(), (k1, k2) -> k1));
            dictMap.putAll(dict2Map);

            // 字典类型
            Collection<Dict> typeList = dictMap.values();
            this.dictManager.saveOrUpdateBatch(typeList);

            // 字典值
            List<DictItem> dictList = dictItemList.stream().map(e -> {
                String key = e.getDictCode() + e.getItemValue();
                Optional<DictItem> dbDict = Optional.ofNullable(dictRecordMap.get(key));
                DictItem dict = dictAssembly.toDictItem(e);
                dict.setDictId(dictMap.get(e.getDictCode()).getId());
                dict.setId(dbDict.map(DictItem::getId).orElse(null));
                return dict;
            }).collect(Collectors.toList());
            this.dictItemManager.saveOrUpdateBatch(dictList);

            Set<String> keys = dictTypes.stream().map(e -> String.format(CacheCons.DICT, e)).collect(Collectors.toSet());
            redisTemplate.delete(keys);

            // 发消息, 删除本地缓存
            dictMqService.resetLocalCache(new DictResetMQ(dictTypes));
            return "ok";
        } catch (BizException | ConstraintViolationException e) {
            log.error("error: ", e);
            throw e;
        } catch (Exception e) {
            log.error("error: ", e);
            throw new BizException("导入失败");
        }
    }

    @Override
    public boolean deleteGroup(DataGroupDeleteReq req) {
        DataGroup firstGroup = dataGroupService.listByIds(req.getIds()).stream().findFirst()
                .orElseThrow(() -> new BizException("删除的数据不存在"));

        DataGroupQuery listReq = new DataGroupQuery();
        listReq.setBizType(DataGroupBizTypeEnum.DICT.getType())
                .setGroupType(firstGroup.getGroupType())
                .setCode(CommonCons.DataGroup.DEFAULT_CODE);
        DataGroupDto defaultGroup = dataGroupService.listGroup(listReq).stream().findFirst()
                .orElseThrow(() -> new BizException("未查询到默认分组, 请联系管理员"));

        // 将被删除的组下面数据移动到默认分组下
        this.dictManager.update(Dict.luw().set(Dict::getGroupId, defaultGroup.getId()).in(Dict::getGroupId, req.getIds()));
        return dataGroupService.deleteGroup(req);
    }

    @Override
    public void exportExcel(HttpServletResponse response, DictQueryReq req) throws Exception {
        List<Dict> dictList = this.list(req);
        List<Long> groupIds = dictList.stream().map(Dict::getGroupId).collect(Collectors.toList());
        groupIds.add(-1L);

        Map<Long, DataGroup> groupMap = dataGroupService.listByIds(groupIds).stream()
            .collect(Collectors.toMap(DataGroup::getId, Function.identity()));


        Map<String, Dict> dictMap = dictList.stream().collect(Collectors.toMap(Dict::getDictCode, Function.identity()));
        List<String> dictCodes = dictList.stream().map(Dict::getDictCode).collect(Collectors.toList());
        dictCodes.add("1");

        List<DictItemExcel> dataItemList = this.dictItemManager.list(DictItem.lqw().in(DictItem::getDictCode, dictCodes))
            .stream().map(dictAssembly::toExcel)
            .peek(e -> e.setDictName(dictMap.get(e.getDictCode()).getDictName()))
            .sorted(Comparator.comparing(DictItemExcel::getDictCode).thenComparing(DictItemExcel::getItemValue))
            .collect(Collectors.toList());
        List<DictExcel> dataList = dictList.stream().map(dictAssembly::toExcel)
            .peek(e -> e.setGroupName(groupMap.getOrDefault(e.getGroupId(), new DataGroup()).getName()))
            .collect(Collectors.toList());


        ExportParams dataParams = new ExportParams();
        dataParams.setTitle("字典信息");
        dataParams.setSheetName("字典");
        dataParams.setStyle(NewExcelExportStylerDefaultImpl.class);
        ExportSheetParams dataSheetParams = new ExportSheetParams()
            .setData(dataList).setEntity(DictExcel.class).setTitle(dataParams);

        ExportParams dataItemParams = new ExportParams();
        dataItemParams.setTitle("值域信息");
        dataItemParams.setSheetName("值域");
        dataItemParams.setStyle(NewExcelExportStylerDefaultImpl.class);
        ExportSheetParams dataItemSheetParams = new ExportSheetParams()
            .setData(dataItemList).setEntity(DictItemExcel.class).setTitle(dataItemParams);

        List<Map<String, Object>> paramsList = new ArrayList<>();
        paramsList.add(dataSheetParams);
        paramsList.add(dataItemSheetParams);
        CusExcelExportUtil.exportExcel(response, paramsList, "字典.xls", ExcelType.HSSF);
    }

    @Override
    public List<DictResp> loadDict(String keyword) {
        if (StringUtils.isEmpty(keyword)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<Dict> lqw = Dict.lqw()
            .eq(Dict::getStatus, UseStats.NORMAL)
            .like(Dict::getDictName, keyword).or().like(Dict::getDictCode, keyword);
        return this.dictManager.list(lqw).stream().map(dictAssembly::toDictResp).collect(Collectors.toList());
    }
}
